package rpcxusage

import (
	"context"
	"errors"
	"fmt"
	"gitee.com/xcy871622040/gameframe/cluster"
	"gitee.com/xcy871622040/gameframe/logger"
	"github.com/smallnest/rpcx/log"
	"github.com/smallnest/rpcx/protocol"
	"github.com/smallnest/rpcx/server"
	"github.com/smallnest/rpcx/share"
	"github.com/xcy8712622040/gnetws"
	"reflect"
	"sync"
	"time"
)

var (
	returned   *service
	xserverSvc *server.Server
)

// Register 注册回访服务
func Register(rcvr interface{}, meta string) error {
	if err := returned.Register(rcvr); err != nil {
		return err
	}

	if xserverSvc != nil {
		if err := xserverSvc.Register(rcvr, meta); err != nil {
			return err
		}
	}

	return nil
}

func initXService(ctx context.Context) {
	returned = &service{
		smap: sync.Map{},
		conn: make(chan *protocol.Message, 100),
		path: cluster.App().Setting().ServiceName,
	}

	// 开始监听服务端推送消息
	go returned.Serve(ctx)

	if host, port := cluster.App().Setting().Host, cluster.App().Setting().ServicePort[cluster.App().Setting().ServiceName]; port != 0 {
		xserverSvc = server.NewServer()
		xserverSvc.Plugins.Add(cluster.App().Runtime.Register())
		go func() {
			go func() {
				<-ctx.Done()
				shutctx, cancel := context.WithTimeout(ctx, 5*time.Second)

				defer cancel()
				if err := xserverSvc.Shutdown(shutctx); err != nil {
					logger.GameLog.Errorf("returned xserver shutdown error:%s", err)
				}
			}()
			if err := xserverSvc.Serve("tcp", fmt.Sprintf("%s:%d", host, port)); err != nil {
				logger.GameLog.Errorf("x-service initXService derror:%s", err.Error())
			}
		}()
	}
}

type handler struct {
	name string

	svct   reflect.Type
	svcr   reflect.Value
	method map[string]*method
}

type service struct {
	path string

	smap sync.Map

	conn chan *protocol.Message
}

func (s *service) Register(rcvr interface{}) error {
	hand := new(handler)
	hand.svct = reflect.TypeOf(rcvr)
	hand.svcr = reflect.ValueOf(rcvr)
	hand.name = reflect.Indirect(hand.svcr).Type().Name()

	if hand.name == "" {
		return errors.New("rpcx.Register: no service name for type " + hand.svct.String())
	} else if !isExported(hand.name) {
		return errors.New("rpcx.Register: type " + hand.name + " is not exported")
	}

	// Install the methods
	hand.method = suitableMethods(hand.svct, true)

	if len(hand.method) == 0 {
		var errorStr string

		// To help the user, see if a pointer receiver would work.
		method := suitableMethods(reflect.PtrTo(hand.svct), false)
		if len(method) != 0 {
			errorStr = "rpcx.Register: type " + hand.name + " has no exported methods of suitable type (hint: pass a pointer to value of that type)"
		} else {
			errorStr = "rpcx.Register: type " + hand.name + " has no exported methods of suitable type"
		}
		log.Error(errorStr)
		return errors.New(errorStr)
	}

	s.smap.Store(hand.name, hand)

	return nil
}

func (s *service) Serve(ctx context.Context) {
	for {
		select {
		case <-ctx.Done():
			logger.GameLog.Warn("agent service close: signcontext.Done")
			return
		case msg := <-s.conn:
			if err := gnetws.GoroutinePool().Submit(func() {
				s.dispatch(ctx, msg)
			}); err != nil {
				logger.GameLog.Errorf("GoroutinePool Submit Error: %s", err)
			}
		}
	}
}

func (s *service) dispatch(ctx context.Context, msg *protocol.Message) {
	ctx = context.WithValue(ctx, share.ReqMetaDataKey, msg.Metadata)
	if val, ok := s.smap.Load(msg.ServicePath); ok {
		hand := val.(*handler)
		if err := hand.method[msg.ServiceMethod].call(ctx, hand.svcr, &request{msg: msg}); err != nil {
			logger.GameLog.Debugf("bidirectional XClient Call: '%s' inexistence", msg.ServicePath)
		}
	} else {
		logger.GameLog.Debugf("bidirectional XClient Call: '%s' inexistence", msg.ServicePath)
	}
}
